package ac.you.api.ask;

import ac.you.api.AcYouAPI;
import ac.you.api.ask.e.AskPlugin;
import ac.you.api.ask.e.ExCondition;
import ac.you.api.ask.e.ResultHandler;
import ac.you.api.ask.e.Tasker;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.bukkit.Bukkit;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.bukkit.scheduler.BukkitTask;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.logging.Level;

@SuppressWarnings("all")
public final class Ask {
    private final List<LivingEntity> entities;
    private Map<Tasker, ResultHandler> mAr;
    private List<AskPlugin> plugins;
    private long totalOutTime;
    private BukkitTask bT;
    private boolean isRunning;
    private Model model;
    private Predicate<Ask> ex;
    private Way way;
    private long startTime;
    private Consumer<Ask> after;
    public boolean hasEntity(LivingEntity entity){
        return entities.contains(entity);
    }
    public List<LivingEntity> getAssociatedEntity(){
        return Collections.unmodifiableList(entities);
    }
    public Model model() {return model;}
    public Way way() {return way;}
    public long startTime() {return startTime;}
    private Ask(List<LivingEntity> entities , List<AskPlugin> plugins, Map<Tasker, ResultHandler> missions, long totalOutTime , Predicate<Ask> exCondition , Model model, Way way) {
        this.entities = entities;
        this.mAr = missions;
        this.totalOutTime = totalOutTime;
        this.model = model;
        this.plugins = plugins;
        this.way = way;
        this.ex = exCondition;
    }
    public static Ask who(LivingEntity... e){
        return new Ask(Collections.synchronizedList(new LinkedList<>(Arrays.asList(e))), null,null,-1, null ,Model.ASSOCIATED,Way.ONEBYONE);
    }
    public Ask condition(Predicate<Ask> condition){
        if (!isRunning()){
            this.ex = condition;
        }
        return this;
    }
    public Ask plugin(AskPlugin... plugin){
        if (!isRunning()){
            if (this.plugins == null){
                this.plugins = Lists.newLinkedList();
            }
            this.plugins.addAll(Arrays.asList(plugin));
        }
        return this;
    }
    public Ask way(Way way){
        if (!isRunning()){
            this.way = way;
        }
        return this;
    }
    public Ask after(Consumer<Ask> after){
        if (!isRunning()){
            this.after = after;
        }
        return this;
    }
    public Ask model(Model model){
        if (!isRunning()) {
            this.model = model;
        }
        return this;
    }
    public <T> Ask todo(ExCondition exCondition , String taskername, Tasker<T> tasker, ResultHandler<T> handler , Consumer<Ask> after){
        if (!isRunning()){
            tasker.i(this);
            tasker.s(Tasker.State.NOSTART);
            tasker.ex(exCondition);
            tasker.h(handler);
            tasker.af(after);
            tasker.na(taskername == null ? "AcTask" + Math.floor(Math.random() * 10)  : taskername);
            handler.tK(tasker);
            if (mAr == null) mAr = Maps.newLinkedHashMap();
            mAr.putIfAbsent(tasker,handler);
        }
        return this;
    }
    public <T> Ask todo(Predicate<Tasker> condition, Runnable fail , String taskername, Tasker<T> tasker, ResultHandler<T> resulthandler , Consumer<Ask> afterdo){
        return !isRunning() ? todo(ExCondition.build(condition,fail), taskername ,tasker,resulthandler,afterdo) : this;
    }
    public boolean isRunning(){
        return this.isRunning;
    }
    public final void flushEntitys(){
        for (LivingEntity entity : getAssociatedEntity()) {
            if (entity instanceof Player && !((Player) entity).isOnline()){
                entities.remove(entity);
            }
        }
    }
    public final void start(long s){
        if (!this.isRunning){
            this.totalOutTime = s * 1000L;
            this.startTime = System.currentTimeMillis();
            this.isRunning = true;
            // 开启任务生命线
            this.bT = Bukkit.getScheduler().runTaskTimerAsynchronously(AcYouAPI.INSTANCE,() -> {
                long timing = System.currentTimeMillis();
                flushEntitys();
                if ( timing - startTime <= this.totalOutTime && entities.size() > 0 && isRunning()){
                    if (this.ex == null || this.ex.test(this)){
                        CompletableFuture tempValue = null;
                        if (mAr.values().stream().allMatch(handler -> handler.value() != null && handler.value().getNow(null) != null)) stop();
                        for (Tasker tasker : mAr.keySet()) {
                            ResultHandler handler = mAr.get(tasker);
                            if (!handler.isBegin()){
                                if (tempValue != null && tempValue.getNow(null) == null && way == Way.ONEBYONE) {
                                    AcYouAPI.log(Level.INFO,"Mission " + tasker + " is holding!");
                                    break;
                                }
                                if (tempValue != null){
                                    tasker.lv(tempValue.getNow(null));
                                }
                                if (tasker.getExCondition().condition.test(tasker)){
                                    plugins.forEach(plugin -> plugin.beforeStart(tasker));
                                    switch (model){
                                        case ASSOCIATED:
                                            switch (way){
                                                case ONEBYONE:
                                                    handler.init(CompletableFuture.supplyAsync(tasker));
                                                    break;
                                                case ALL:
                                                    throw new NullPointerException("Cannot support ASSOCIATED and ALL.");
                                            }
                                            break;
                                        case UNASSOCIATED:
                                            handler.init(CompletableFuture.runAsync(tasker));
                                    }
                                } else {
                                    tasker.getExCondition().fail.run();
                                }
                            } else {
                                // plugin
                                plugins.forEach(plugin -> plugin.runTime(tasker));
                                // after
                                final Object var = handler.value().getNow(null);
                                if (var != null){
                                    plugins.forEach(plugin -> plugin.whenGetResult(tasker));
                                    if (tasker.getAfter() != null && !tasker.isIsrunafter()){
                                        tasker.getAfter().accept(this);
                                        tasker.ra(true);
                                        HandlerList.unregisterAll(tasker);
                                    }
                                }
                            }
                            tempValue = handler.value();
                        }
                    }
                } else stop();
            } ,0,10);
        }
    }
    public final void stop(){
        if (this.isRunning){
            this.isRunning = false;
            if (this.after != null) this.after.accept(this);
            mAr.forEach((tasker, handler) -> {
                HandlerList.unregisterAll(tasker);
                if (handler.value().getNow(null) == null) handler.finish(null);
                plugins.forEach(plugin -> plugin.afterDo(tasker));
            });
            Bukkit.getScheduler().cancelTask(this.bT.getTaskId());
            AcYouAPI.log(Level.INFO,"Shutdown the bukkit task-line: " + this.bT.getTaskId());
        }
    }
    public enum Model{
        ASSOCIATED,
        UNASSOCIATED
    }
    public enum Way{
        ONEBYONE,
        ALL
    }
}
